// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/**
*@describe:
*@author wfl19/Kristas
*@date 2021/12/17
 */

package app_context

import (
	"errors"
	"fmt"
	"gitee.com/kristas/booting-go/framework/common/util/list"
	"gitee.com/kristas/booting-go/framework/common/util/reflectx"
	. "gitee.com/kristas/booting-go/framework/core/bean"
	"gitee.com/kristas/booting-go/framework/core/statement"
	"github.com/modern-go/concurrent"
	"reflect"
	"sync"
)

//var Context = NewAppCtx()

func NewAppCtx() ApplicationContext {
	return &appCtx{
		singletonObjects:     concurrent.NewMap(),
		registeredSingletons: list.NewConcurrentSets(),
		lazyBean:             concurrent.NewMap(),
	}
}

type appCtx struct {
	singletonObjects     *concurrent.Map
	registeredSingletons list.Set
	lazyBean             *concurrent.Map
	lock                 sync.Mutex
}

func (c *appCtx) GetBean(name string) (interface{}, error) {
	if c.IsBeanExists(name) {
		value, _ := c.singletonObjects.Load(name)
		return value, nil
	}
	return nil, errors.New("bean not found: " + name)
}

func (c *appCtx) IsBeanExists(name string) bool {
	return c.registeredSingletons.Exists(name)
}

func (c *appCtx) RegisterBean(bean interface{}) {
	c.computeStatement(bean)
	c.register(GetName(bean), bean)
}

func (c *appCtx) RegisterBeanWithName(name string, bean interface{}) {
	c.computeStatement(bean)
	c.register(name, bean)
}

func (c *appCtx) RegisterFactoryBean(bean FactoryBean) {
	if bean.IsSingleton() {
		c.computeStatement(bean)
		c.registerSingletonFactoryBean(bean)
	}
}

func (c *appCtx) GetBeansByInterfaceType(typ reflect.Type) []interface{} {
	return c.GetBeansByInterface(reflect.New(typ).Interface())
}

func (c *appCtx) GetBeanByInterface(a interface{}) interface{} {
	beans := c.GetBeansByInterface(a)
	return beans[0]
}

func (c *appCtx) GetBeansByInterface(a interface{}) []interface{} {
	var beans = make([]interface{}, 0)
	c.singletonObjects.Range(func(k interface{}, v interface{}) bool {
		if reflectx.IsImplement(v, a) {
			beans = append(beans, v)
		}
		return true
	})
	if len(beans) == 0 {
		c.tryInitLazyBean(reflect.TypeOf(a).Elem())
		c.singletonObjects.Range(func(k interface{}, v interface{}) bool {
			if reflectx.IsImplement(v, a) {
				beans = append(beans, v)
			}
			return true
		})
	}
	return beans
}

func (c *appCtx) GetAllBeans() []Bean {
	return c.GetAllBeansByInterface(new(Bean))
}

func (c *appCtx) GetAllBeansByInterface(a interface{}) []Bean {
	values := c.GetBeansByInterface(a)
	var components []Bean
	for i := range values {
		components = append(components, values[i].(Bean))
	}
	return components
}

func (c *appCtx) registerSingletonFactoryBean(bean FactoryBean) {
	c.lazyBean.Store(bean, bean.GetBean)
}

func (c *appCtx) register(name string, obj interface{}) {
	if c.IsBeanExists(name) {
		panic(fmt.Sprintf("duplicate bean: %s", name))
	}
	c.singletonObjects.Store(name, obj)
	c.registeredSingletons.Put(name)
}

type lazyBeanFunc = func() (Bean, error)

func (c *appCtx) tryInitLazyBean(p reflect.Type) {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.lazyBean.Range(func(key, getBeanFunc interface{}) bool {
		factoryBean := key.(FactoryBean)
		beanType := factoryBean.GetBeanType()
		if beanType == p || beanType.Implements(p) {
			f := getBeanFunc.(lazyBeanFunc)
			bean, err := f()
			if err != nil {
				panic(err)
			}
			c.RegisterBean(bean)
			c.lazyBean.Delete(key)
		}
		return true
	})
}

func (c *appCtx) computeStatement(bean interface{}) {
	if state, ok := bean.(statement.Statement); ok {
		statement.Compile(state)
	}
}
